﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Xml;

using Spencen.Mobile.Markup.Converters;
using Spencen.Mobile.Markup.Extensions;
using Spencen.Mobile.UI;
using Spencen.Mobile.UI.Primitives;
using Spencen.Mobile.UI.Markup.Converters;
using Spencen.Mobile.UI.Markup.Extensions;

namespace Spencen.Mobile.Markup
{
    public class XamlRenderer
    {
        private static readonly IDictionary<string, Type> _resolvedTypes = new Dictionary<string, Type>();

        private IDrawingHost _host;
        private object _dataContext;
        private readonly IDictionary<string, object> _resourceDictionary;

        public XamlRenderer( IDrawingHost host, object dataContext )
        {
            _host = host;
            _dataContext = dataContext;
            _resourceDictionary = new Dictionary<string, object>();
        }

        public object FindResource( string resourceName )
        {
            return _resourceDictionary[ resourceName ];
        }

        public object Render( System.IO.Stream stream )
        {
            var stopWatch = System.Diagnostics.Stopwatch.StartNew();

            var xmlReader = new XmlTextReader( stream );
            var xamlReader = new XamlReader();
            xamlReader.Converters.Add( typeof( Padding ), new PaddingConverter() );
            xamlReader.Converters.Add( typeof( DesiredSize ), new SizeConverter() );
            xamlReader.MarkupExtensions.Add( new TemplateBindingMarkupExtension() );
            var rootElement = xamlReader.Read( xmlReader );

            System.Diagnostics.Debug.WriteLine( string.Format( "Read took {0} milliseconds.", stopWatch.ElapsedMilliseconds ) );
            stopWatch.Reset();
            stopWatch.Start();

            xamlReader.Resolve( rootElement );

            System.Diagnostics.Debug.WriteLine( string.Format( "Resolve took {0} milliseconds.", stopWatch.ElapsedMilliseconds ) );
            stopWatch.Reset();
            stopWatch.Start();

            var output =  Render( null, rootElement );

            System.Diagnostics.Debug.WriteLine( string.Format( "Render took {0} milliseconds.", stopWatch.ElapsedMilliseconds ) );

            return output;
        }

        public object Render( object parentInstance, XamlElement rootElement )
        {
            if ( parentInstance == null )
                rootElement.DataContext = _dataContext;

            var instance = CreateInstance( parentInstance, rootElement );

            var supportInitialize = instance as ISupportInitialize;
            if ( supportInitialize != null )
                supportInitialize.BeginInit();

            if ( instance is DrawingElement && parentInstance is DrawingContainer )
                ( (DrawingContainer)parentInstance ).Children.Add( (DrawingElement)instance );

            if ( rootElement.IsNamed )
                _resourceDictionary.Add( rootElement.ResourceName, instance );

            SetProperties( parentInstance, instance, rootElement );

            var drawingContainer = instance as DrawingContainer;

            // Process complex properties - i.e. those declared as Elements with possible child nodes representing the value.
            foreach ( var property in rootElement.Properties )
            {
                foreach ( var subChildElement in property.Children )
                {
                    if ( typeof( XamlElement ).IsAssignableFrom( property.PropertyDescriptor.PropertyType ) )
                        SetProperty( instance, property.MemberName, subChildElement, subChildElement.ResourceKey, property.PropertyDescriptor );
                    else
                    {
                        var propertyValue = Render( instance, subChildElement );
                        SetProperty( instance, property.MemberName, propertyValue, subChildElement.ResourceKey, property.PropertyDescriptor );
                    }
                }
            }

            // Process the child elements
            foreach ( var childElement in rootElement.Children )
                Render( instance, childElement );

            if ( supportInitialize != null )
                supportInitialize.EndInit();

            return instance;
        }

        private void SetProperties( object parentInstance, object instance, XamlElement rootElement )
        {
            var drawingElement = parentInstance as DrawingElement;
            foreach ( var property in rootElement.Attributes )
                SetProperty( instance, property );
        }

        private void SetProperty( object instance, XamlAttribute elementProperty )
        {
            System.Diagnostics.Debug.Assert( elementProperty.PropertyDescriptor != null );
            SetProperty( instance, elementProperty.Name, GetPropertyValue( instance, elementProperty ), null, elementProperty.PropertyDescriptor );
        }

        private void SetProperty( object instance, string propertyName, object value, string key, PropertyDescriptor property )
        {
            if ( property.IsReadOnly )
            {
                var currentValue = property.GetValue( instance );
                var genericCollection = currentValue as System.Collections.IDictionary;
                if ( genericCollection != null )
                {
                    if ( string.IsNullOrEmpty( key ) )
                        throw new InvalidOperationException( "Must supply a key for elements that are to be added to a dictionary." );
                    else
                        genericCollection.Add( key, value );
                }
                else
                {
                    var collection = currentValue as System.Collections.IList;
                    if ( collection != null )
                        collection.Add( value );
                    else
                        throw new InvalidOperationException( string.Format( "Property {0} on {1} is read-only.", propertyName, instance.GetType().Name ) );
                }
            }
            else
                property.SetValue( instance, value );
        }

        private object CreateInstance( object parentInstance, XamlElement rootElement )
        {
            Type type = rootElement.Type;

            // Special case constructor for objects that take IDrawingHost - should be a hook into XamlReader, e.g. IConstructorProvider.
            if ( rootElement.Constructor == null )
            {
                var constructors = type.GetConstructors( BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.Instance );
                var hostConstructor = ( from c in constructors where c.GetParameters().Any( p => p.ParameterType == typeof( IDrawingHost ) ) select c ).FirstOrDefault();
                if ( hostConstructor != null )
                    return hostConstructor.Invoke( new object[] { _host } );
            }

            var bestConstructor = rootElement.Constructor;
            var args = rootElement.ConstructorArgs;
            if ( bestConstructor != null )
                return bestConstructor.Invoke( args.Select( a => a.ConvertedValue ).ToArray() );
            else
                return Activator.CreateInstance( type );
        }

        private object GetPropertyValue( object parentInstance, XamlAttribute property )
        {
            var propertyValue = property.ConvertedValue;

            var dynamicValue = property.ConvertedValue as IDynamicValueProvider;
            if ( dynamicValue != null )
                propertyValue = dynamicValue.GetValue( property, parentInstance );

            // TODO: More type converter stuff required
            if ( property.PropertyDescriptor.PropertyType == typeof( string ) )
                propertyValue = propertyValue.ToString();

            return propertyValue;
        } 
     }
}
